/**
 * 
 */
package org.quincy.rock.core.security;

import java.util.Date;

import org.apache.commons.lang3.StringUtils;
import org.quincy.rock.core.util.CoreUtil;
import org.quincy.rock.core.util.DateUtil;
import org.quincy.rock.core.util.StringUtil;

/**
 * 关于安全的工具类
 * 
 * @author wks
 * @version 1.0
 * @since 1.0
 */
public class SecurityUtils {

	private final static long ADD = 312;

	/**
	 * 修正Key
	 * 
	 * @param key 需要修正的Key
	 * @return 修正后的key
	 */
	public static String correctKey(String key) {
		int len = key.length();
		if (len >= 8 && len < 16)
			key = StringUtils.leftPad(key, 16, '8');
		else if (len > 16 && len < 32)
			key = StringUtils.leftPad(key, 32, '9');
		return key;
	}

	private static String makeActivationCode(String publicCode, Crypto crypto, Date expireDate, Date firtErrorDate,
			int trialDays) {
		StringBuilder sb = new StringBuilder();
		String toDate = StringUtil.leftPad(
				Long.toHexString(expireDate.getTime() + DateUtil.MILLISECOND_UNIT_WEEK * ADD), 12, StringUtil.CHAR_0);
		toDate += StringUtil.leftPad(
				Long.toHexString(
						firtErrorDate == null ? 0 : firtErrorDate.getTime() + DateUtil.MILLISECOND_UNIT_WEEK * ADD),
				12, StringUtil.CHAR_0);
		toDate += Long.toHexString(trialDays + ADD);
		byte[] data = crypto.encrypt(toDate.getBytes(StringUtil.ISO_8859_1));
		sb.append("1F");
		sb.append(StringUtil.leftPad(Integer.toHexString(data.length + 3), 4, StringUtil.CHAR_0));
		sb.append(CoreUtil.byteArray2HexString(data));
		publicCode += sb.toString();
		data = crypto.encrypt(publicCode.getBytes(StringUtil.ISO_8859_1));
		sb.append(CoreUtil.byteArray2HexString(data));
		return sb.toString();
	}

	/**
	 * 计算激活码的函数
	 * 
	 * @param publicCode 序列号
	 * @param key        加密key,8-32个字符
	 * @param expireDate 激活码到期日期
	 * @param trialDays  激活码到期后的试用天数
	 * @return 激活码
	 */
	public static String makeActivationCode(String publicCode, String key, Date expireDate, int trialDays) {
		key = correctKey(key);
		AESCrypto crypto = new AESCrypto();
		crypto.init(key.getBytes());
		return makeActivationCode(publicCode, crypto, expireDate, null, trialDays);
	}

	/**
	 * 为正常的激活码打错误标记(标记第一次错误日期)。 已经打了错误标记的激活码不会重复打错误标记，而是直接返回。
	 * 
	 * @param activationCode 正常的激活码
	 * @param key            加密key,8-32个字符
	 * @param firtErrorDate  第一次错误日期,不能为null
	 * @return 打了错误标记(第一次错误日期)的激活码
	 */
	public static String makeErrorActivationCode(String activationCode, String key, Date firtErrorDate) {
		key = correctKey(key);
		AESCrypto crypto = new AESCrypto();
		crypto.init(key.getBytes());
		int len = Integer.parseInt(activationCode.substring(2, 6), 16) << 1;
		byte[] data = CoreUtil.hexString2ByteArray(activationCode.substring(6, len));
		data = crypto.decrypt(data);
		String toDate = new String(data, StringUtil.ISO_8859_1);
		long time = Long.parseLong(toDate.substring(12, 24), 16) - DateUtil.MILLISECOND_UNIT_WEEK * ADD;
		if (time > 0 || firtErrorDate == null)
			return activationCode;
		Date expireDate = new Date(Long.parseLong(toDate.substring(0, 12), 16) - DateUtil.MILLISECOND_UNIT_WEEK * ADD);
		int trialDays = Integer.parseInt(toDate.substring(24), 16) - (int) ADD;
		data = CoreUtil.hexString2ByteArray(activationCode.substring(len));
		data = crypto.decrypt(data);
		String publicCode = new String(data, 0, data.length - len, StringUtil.ISO_8859_1);
		return makeActivationCode(publicCode, crypto, expireDate, firtErrorDate, trialDays);
	}

	/**
	 * 清除错误标记，返回正常的激活码。 清除激活码中的第一次错误日期。
	 * 
	 * @param errorActivationCode 打了错误标记的激活码
	 * @param key                 加密key,8-32个字符
	 * @return 正常的激活码
	 */
	public static String clearFirstErrorDate(String errorActivationCode, String key) {
		key = correctKey(key);
		AESCrypto crypto = new AESCrypto();
		crypto.init(key.getBytes());
		int len = Integer.parseInt(errorActivationCode.substring(2, 6), 16) << 1;
		byte[] data = CoreUtil.hexString2ByteArray(errorActivationCode.substring(6, len));
		data = crypto.decrypt(data);
		String toDate = new String(data, StringUtil.ISO_8859_1);
		long time = Long.parseLong(toDate.substring(12, 24), 16) - DateUtil.MILLISECOND_UNIT_WEEK * ADD;
		if (time == 0)
			return errorActivationCode;
		Date expireDate = new Date(Long.parseLong(toDate.substring(0, 12), 16) - DateUtil.MILLISECOND_UNIT_WEEK * ADD);
		int trialDays = Integer.parseInt(toDate.substring(24), 16) - (int) ADD;
		data = CoreUtil.hexString2ByteArray(errorActivationCode.substring(len));
		data = crypto.decrypt(data);
		String publicCode = new String(data, 0, data.length - len, StringUtil.ISO_8859_1);
		return makeActivationCode(publicCode, crypto, expireDate, null, trialDays);
	}

	/**
	 * 解析激活码到期日
	 * 
	 * @param activationCode 激活码
	 * @param key            解密key,可以是16位或32位
	 * @return 激活码到期日
	 */
	public static Date parseExpireDate(String activationCode, String key) {
		key = correctKey(key);
		int len = Integer.parseInt(activationCode.substring(2, 6), 16) << 1;
		byte[] data = CoreUtil.hexString2ByteArray(activationCode.substring(6, len));
		AESCrypto crypto = new AESCrypto();
		crypto.init(key.getBytes());
		data = crypto.decrypt(data);
		String toDate = new String(data, StringUtil.ISO_8859_1);
		return new Date(Long.parseLong(toDate.substring(0, 12), 16) - DateUtil.MILLISECOND_UNIT_WEEK * ADD);
	}

	/**
	 * 解析激活码第1次错误日期。
	 * 当验证未过期激活码有效性时，因为硬件信息变动原因造成激活码失效，那么，造成激活码失效的原因可能是盗版，也有可能是用户机器换了网卡硬盘等造成的。
	 * 第一次发现硬件变动造成激活码失效的时间即为错误日期。在这种情况下，可能允许试用期一段时间，而不是立即停用系统，以免造成合法用户的损失。
	 * 
	 * @param activationCode 激活码
	 * @param key            解密key,可以是16位或32位
	 * @return 激活码第1次错误日期，如果激活码没有错误，则返回null
	 */
	public static Date parseFirstErrorDate(String activationCode, String key) {
		key = correctKey(key);
		int len = Integer.parseInt(activationCode.substring(2, 6), 16) << 1;
		byte[] data = CoreUtil.hexString2ByteArray(activationCode.substring(6, len));
		AESCrypto crypto = new AESCrypto();
		crypto.init(key.getBytes());
		data = crypto.decrypt(data);
		String toDate = new String(data, StringUtil.ISO_8859_1);
		long time = Long.parseLong(toDate.substring(12, 24), 16) - DateUtil.MILLISECOND_UNIT_WEEK * ADD;
		return time > 0 ? new Date(time) : null;
	}

	/**
	 * 解析激活码到期后的试用天数
	 * 
	 * @param activationCode 激活码
	 * @param key            解密key,8-32个字符
	 * @return 激活码到期后的试用天数
	 */
	public static int parseTrialDay(String activationCode, String key) {
		key = correctKey(key);
		int len = Integer.parseInt(activationCode.substring(2, 6), 16) << 1;
		byte[] data = CoreUtil.hexString2ByteArray(activationCode.substring(6, len));
		AESCrypto crypto = new AESCrypto();
		crypto.init(key.getBytes());
		data = crypto.decrypt(data);
		String toDate = new String(data, StringUtil.ISO_8859_1);
		return Integer.parseInt(toDate.substring(24), 16) - (int) ADD;
	}

	/**
	 * 解析序列号
	 * 
	 * @param activationCode 激活码
	 * @param key            解密key,8-32个字符
	 * @return publicCode
	 */
	public static String parsePublicCode(String activationCode, String key) {
		key = correctKey(key);
		int len = Integer.parseInt(activationCode.substring(2, 6), 16) << 1;
		byte[] data = CoreUtil.hexString2ByteArray(activationCode.substring(len));
		AESCrypto crypto = new AESCrypto();
		crypto.init(key.getBytes());
		data = crypto.decrypt(data);
		String publicCode = new String(data, 0, data.length - len, StringUtil.ISO_8859_1);
		return publicCode;
	}
}
